/*
 * File         : cym_timer.c
 * Version      : 0.7.1
 * Author       : Vincent Cheung
 * Date         : Sept. 23, 2013
 * Description  : All the operation of timer for application.
 *
 * Copyright (C) 2013, CYM Co., Ltd.
 *
 * Change Logs  :
 * Date                 Author          Notes
 * Sept. 23, 2013       Vincent         first version
 * Oct. 10, 2013        Vincent         add cym_timer_init function
 * Oct. 11, 2013        Vincent         add _cym_timer_init function
 * Oct. 30, 2013        Vincent         add soft timer support and 
 *                                      high precision timer support
 *                                      add _cym_timer_check function
 * Oct. 31, 2013        Vincent         add parameter to cym_timer_init and
 *                                      cym_timer_create
 * Nov. 01, 2013        Vincent         change the parameter time's type of 
 *                                      cym_timer_init, cym_timer_create to utime_t
 * Nov. 04, 2013        Vincent         fix bug for _cym_timer_check
 * Nov. 11, 2013        Vincent         fix bug for cym_timer_init
 * Nov. 13, 2013        Vincent         add sync support, fix bugs
 * Nov. 14, 2013        Vincent         add half hz support, fix bugs
 *
 */

#include "cym_system.h"
#ifdef CYM_USING_MALLOC
#include <stdlib.h>
#endif
#include <string.h>

/* hard timer list */
static list_t cym_timer_list = CYM_LIST_OBJECT_INIT(cym_timer_list);

#ifdef CYM_USING_SOFT_TIMER
/* soft timer list */
static list_t cym_soft_timer_list = CYM_LIST_OBJECT_INIT(cym_soft_timer_list);
#endif

#ifdef CYM_USING_HIGH_PREC_TIMER
/* high precision hard timer list */
static list_t cym_high_prec_timer_list = CYM_LIST_OBJECT_INIT(cym_high_prec_timer_list);
#endif

static struct sync_info information;

/**
 * This function is to initilize a timer, called by kernel
 *
 * @param timer the created timer
 * @param timeout the timeout callback function
 * @param parameter the parameter of timeout callback function
 * @param time the interval time(ms)
 * @param period_flag the flag of timer period(one_shot or periodic)
 * @param type_flag the flag of timer type
 *
 * @retval NULL
 */
static void _cym_timer_init(p_timer_t timer, void (*timeout)(void *parameter),
                            void *parameter, utime_t time, 
                            uint8_t period_flag, uint8_t type_flag)
{
	utick_t tick = 0;

#ifdef CYM_USING_HIGH_PREC_TIMER
	/* if the timer is high precision timer, then caculate the tick */
	if (type_flag == CYM_TIMER_FLAG_HIGH_PREC_TIMER)
	{
		tick = (time * CYM_HIGH_PREC_TICK_PRE_SECOND + 999) / 1000;
	}
	else
#endif
	{
		/* caculate the tick */
		tick = (time * CYM_SYSTEM_TICK_PER_SECOND + 999) / 1000;
	}

	memset(timer, 0, sizeof(*timer));

        /* set flag */
	timer->parent.flag   = period_flag | type_flag;

	/* set deactivated */
	timer->parent.flag  &= ~CYM_TIMER_FLAG_ACTIVATED;

	timer->timeout_func  = timeout;
	timer->parameter     = parameter;

	timer->interval_tick = tick;
	timer->timeout_tick  = 0;

	/* initialize list node in timer structure */
	cym_list_init(&(timer->list));
}

/**
 * @addtogroup TimerAndClock
 */

/*@{*/

/**
 * This function to initialize a created timer, normally this function is used
 * to initialize a static timer if CPU doesn't support malloc. If CPU supports
 * malloc, you can also use cym_timer_create.
 *
 * @see cym_timer_create
 *
 * @param timer the created timer
 * @param timeout the timeout callback function
 * @param parameter the parameter of timeout callback function
 * @param time the interval time(ms),the maximum value shall less than CYM_TICK_MAX / 2
 * @param period_flag the flag of timer period(one_shot or periodic)
 * @param type_flag the flag of timer type
 *
 * @return return CYM_EOK if initialize successfully, otherwise, return -CYM_ERROR
 */
serr_t cym_timer_init(p_timer_t timer, void (*timeout)(void *parameter),
                         void *parameter, utime_t time, 
                         uint8_t period_flag, uint8_t type_flag)
{
	if (timer == CYM_NULL)
	{
		return -CYM_ERROR;
	}

	_cym_timer_init(timer, timeout, parameter, time, period_flag, type_flag);

	return CYM_EOK;
}

/**
 * This function to create a new timer, normally this function is used to
 * initialzie a dynamic timer if CPU support malloc(), otherwise, it supposes
 * to use cym_timer_init function.
 *
 * @see cym_timer_init
 *
 * @note if the malloc is not supported, do not use this function
 *
 * @param timeout the timeout callback function
 * @param parameter the parameter of timeout callback function
 * @param time the interval time(ms), the maximum value shall less than CYM_TICK_MAX / 2
 * @param period_flag the flag of timer period(one_shot or periodic)
 * @param type_flag the flag of timer type
 *
 * @return the created timer
 */
p_timer_t cym_timer_create(void (*timeout)(void *parameter), void *parameter, 
                             utime_t time, uint8_t period_flag, 
                             uint8_t type_flag)
{
	struct k_timer *timer = CYM_NULL;
#ifdef CYM_USING_MALLOC
	/* allocate the memory for timer */
	timer = malloc(sizeof(*timer));
#endif
	if (timer == CYM_NULL)
	{
		return CYM_NULL;
	}

        _cym_timer_init(timer, timeout, parameter, time, period_flag, type_flag);

	return timer;
}

/**
 * This function is to delete a timer from a timer list. Normally this function
 * is used to delete a dynamic timer from system.
 *
 * @param timer the timer to be deleted
 *
 * @return return CYM_EOK if timer was deleted, otherwise return -CYM_ERROR
 */
serr_t cym_timer_delete(p_timer_t timer)
{
	if (timer != CYM_NULL)
	{
		/* disable interrupt */
		cym_interrupt_disable();

		/* remove timer from timer list */
		cym_list_remove(&(timer->list));

		/* enable interrupt */
		cym_interrupt_enable();
#ifdef CYM_USING_MALLOC
		/* free memory */
		free(timer);
#else
		memset(timer, 0, sizeof(*timer));
#endif
		return CYM_EOK;
	}

	return -CYM_ERROR;
}

/**
 * This function is used to start a timer.
 *
 * @param timer the timer to be started
 *
 * @return return CYM_EOK if timer start, otherwise return -CYM_ERROR
 */
serr_t cym_timer_start(p_timer_t timer)
{
	struct k_timer *t;
	list_t *node, *timer_list;

	if (timer != CYM_NULL)
	{
		/* the timer has started */
		if (timer->parent.flag & CYM_TIMER_FLAG_ACTIVATED)
		{
			return -CYM_ERROR;
		}

		/* set timout tick */
#ifdef CYM_USING_HIGH_PREC_TIMER
		if (timer->parent.flag & CYM_TIMER_FLAG_HIGH_PREC_TIMER)
		{
			timer->timeout_tick = cym_tick_high_prec_get() + timer->interval_tick;
		}
		else
#endif	/* CYM_USING_HIGH_PREC_TIMER */
		{
			timer->timeout_tick = cym_tick_get() + timer->interval_tick;
		}

		/* disable interrupt */
		cym_interrupt_disable();

#ifdef CYM_USING_HIGH_PREC_TIMER
		if (timer->parent.flag & CYM_TIMER_FLAG_HIGH_PREC_TIMER)
		{
			/* insert timer into high precision timer list */
			timer_list = &cym_high_prec_timer_list;
		}
		else 
#endif	/* CYM_USING_HIGH_PREC_TIMER */
#ifdef CYM_USING_SOFT_TIMER
		if (timer->parent.flag & CYM_TIMER_FLAG_SOFT_TIMER)
		{
			/* insert timer into soft timer list */
			timer_list = &cym_soft_timer_list;
		}
		else
#endif	/* CYM_USING_SOFT_TIMER */
		{
			/* insert timer into timer list */
			timer_list = &cym_timer_list;
		}

		for (node = timer_list->next; node != timer_list; node = node->next)
		{
			t = CYM_LIST_ENTRY(node, struct k_timer, list);
			/*
			 * If there are two timers that timeout at the same time,
			 * the timer inserted early get called early.
			 */
			if ((t->timeout_tick - timer->timeout_tick) == 0)
			{
				cym_list_insert_after(node, &(timer->list));

				break;
			}
			/*
			 * It supposes that the new tick should less than half 
			 * of maximum tick.
			 */
			else if((t->timeout_tick - timer->timeout_tick) < CYM_TICK_MAX / 2)
			{
				cym_list_insert_before(node, &(timer->list));

				break;
			}
		}

		/* not found suitable position in timer list */
		if (node == timer_list)
		{
			cym_list_insert_before(node, &(timer->list));
		}

		/* set the timer flag */
		timer->parent.flag |= CYM_TIMER_FLAG_ACTIVATED;

		/* enable interrupt */
		cym_interrupt_enable();

		return CYM_EOK;
	}

	return -CYM_ERROR;
}

/**
 * This function is used to stop a timer in system.
 *
 * @param timer the timer to be stopped
 * 
 * @return return CYM_EOK when successfully, otherwise, return -CYM_ERROR
 */
serr_t cym_timer_stop(p_timer_t timer)
{
	/* the timer shall not be NULL */
	if (timer == CYM_NULL)
	{
		return -CYM_ERROR;
	}

	/* to check if the timer is activated */
	if (!(timer->parent.flag & CYM_TIMER_FLAG_ACTIVATED))
	{
		return -CYM_ERROR;
	}

	/* disable interrupt */
	cym_interrupt_disable();

	/* remove timer from timer list */
	cym_list_remove(&(timer->list));

	/* enable interrupt */
	cym_interrupt_enable();

	/* change the flag of timer */
	timer->parent.flag &= ~CYM_TIMER_FLAG_ACTIVATED;

	return CYM_EOK;
}

/**
 * This function is used to control the started timer in system.
 * 
 * @param timer the timer to get or set
 * @param cmd the control command
 * @param arg the argument
 *
 * @return return CYM_EOK
 */
serr_t cym_timer_control(p_timer_t timer, uint8_t cmd, void *arg)
{
	/* the timer shall not be null */
	if (timer != CYM_NULL)
	{
		switch (cmd)
		{
		case CYM_TIMER_CTRL_SET_TIME:
			timer->interval_tick = *(utick_t *)arg;
			break;

		case CYM_TIMER_CTRL_GET_TIME:
			*(utick_t *)arg = timer->interval_tick;
			break;

		case CYM_TIMER_CTRL_SET_ONE_SHOT:
			timer->parent.flag &= ~CYM_TIMER_FLAG_PERIODIC;
			break;

		case CYM_TIMER_CTRL_SET_PERIODIC:
			timer->parent.flag |= CYM_TIMER_FLAG_PERIODIC;

		default:
			break;
		}
	}

	return CYM_EOK;
}

/**
 * To get the synch information which is need by application.
 *
 * @param frequency the synch frequency
 *
 * @return return sync_info
 */
struct sync_info *cym_sync_info_get(uint8_t frequency)
{
	struct sync_info *info = &information;
	utick_t current_tick;

	/* get current tick through specified clock timer */
	current_tick = cym_tick_get();

	switch (frequency)
	{
	case CYM_HALF_HZ_SYNC:
		info->sync_flag = (current_tick / CYM_SYSTEM_TICK_PER_SECOND) & 0x01;
		info->pass_time = current_tick % CYM_SYSTEM_TICK_PER_SECOND;
		break;

	case CYM_1HZ_SYNC:
		/* caculate sync_flag and pass_time via CYM_SYSTEM_TICK_PER_SECOND */
		info->sync_flag = (current_tick / (CYM_SYSTEM_TICK_PER_SECOND >> 1)) & 0x01;
		info->pass_time = current_tick % (CYM_SYSTEM_TICK_PER_SECOND >> 1);
		break;

	case CYM_2HZ_SYNC:
		info->sync_flag = (current_tick / (CYM_SYSTEM_TICK_PER_SECOND >> 2)) & 0x01;
		info->pass_time = current_tick % (CYM_SYSTEM_TICK_PER_SECOND >> 2);
		break;

	default:
		break;
	}

	return info;
}

/*@}*/

/**
 * This function is to check a timer list, and it's invoked by kernel. 
 *
 * @note do not invoke this function in interrupt or any other place.
 *
 * @param timer_list the timer list to be checked
 * @param tick_get the tick get callback function
 *
 * @retval NULL
 */
static void _cym_timer_check(list_t *timer_list, utick_t (*tick_get)(void))
{
	struct k_timer *t;
	utick_t current_tick;

	/* get current tick through specified clock timer */
	current_tick = tick_get();

	/* disable interrupt */
	cym_interrupt_disable();

	/* if the timer list is not empty */
	while (!cym_list_is_empty(timer_list))
	{
		t = CYM_LIST_ENTRY(timer_list->next, struct k_timer, list);

		/* the timer is timeout */
		if (current_tick - t->timeout_tick < CYM_TICK_MAX / 2)
		{
			/* remove timer from timer list */
			cym_list_remove(&(t->list));

			/* invoke the timeout callback function */
			t->timeout_func(t->parameter);

			/* re-get the current tick */
			current_tick = tick_get();

			/*
			 * if the timer is periodic, restart it
			 * and to avoid that the timer flag modified by user 
			 */
			if ((t->parent.flag & CYM_TIMER_FLAG_PERIODIC) &&
			    (t->parent.flag & CYM_TIMER_FLAG_ACTIVATED) &&
			    ((t->parent.flag & CYM_TIMER_FLAG_HARD_TIMER) ||
			     (t->parent.flag & CYM_TIMER_FLAG_HIGH_PREC_TIMER)))
			{
				t->parent.flag &= ~CYM_TIMER_FLAG_ACTIVATED;

					cym_timer_start(t);
			}
			else if ((t->parent.flag & CYM_TIMER_FLAG_ONE_SHOT) &&
				 ((t->parent.flag & CYM_TIMER_FLAG_HARD_TIMER) ||
				  (t->parent.flag & CYM_TIMER_FLAG_HIGH_PREC_TIMER)))
			{
				/* set deactivated flag */
				t->parent.flag &= ~CYM_TIMER_FLAG_ACTIVATED;
			}
		}
		else
		{
			break;
		}
	}

	/* enable interrupt */
	cym_interrupt_enable();
}

/**
 * @addtogroup TimerAndClock
 */

/*@{*/

/**
 * This function is used to check timer list, if a timeout event happens,
 * the corresponding callback function will be invoked.
 *
 * @note this function is invoked in system tick timer interrupt
 *
 * @retval NULL
 */
void cym_timer_check(void)
{
	_cym_timer_check(&cym_timer_list, cym_tick_get);
}

#ifdef CYM_USING_HIGH_PREC_TIMER
/**
 * This function is used to check high precision timer list, if a timeout event 
 * happens, the corresponding callback function will be invoked.
 *
 * @note this fuction must invoked in high precision timer interrupt
 *
 * @retval NULL
 */
void cym_high_prec_timer_check(void)
{
	_cym_timer_check(&cym_high_prec_timer_list, cym_tick_high_prec_get);
}
#endif

#ifdef CYM_USING_SOFT_TIMER
/**
 * This function is used to check soft timer list, if a timeout happens, the
 * correspoding timeout function will be invoked.
 *
 * @note this function is invoked in main loop
 *
 * @retval NULL
 */
void cym_soft_timer_check(void)
{
	struct k_timer *t;
	utick_t current_tick;

	/* get the current tick */
	current_tick = cym_tick_get();

	while (!cym_list_is_empty(&cym_soft_timer_list))
	{
		t = CYM_LIST_ENTRY(cym_soft_timer_list.next, struct k_timer, list);

		if ((current_tick - t->timeout_tick) < CYM_TICK_MAX / 2)
		{
			/* remove timer from timer list */
			cym_list_remove(&(t->list));

			/* invoke the timeout callback function */
			t->timeout_func(t->parameter);

			/* re-get the current tick */
			current_tick = cym_tick_get();

			/*
			 * to check if the timer is a periodic timer
			 * and to avoid that the timer flag modified by user 
			 */
			if ((t->parent.flag & CYM_TIMER_FLAG_PERIODIC) &&
			    (t->parent.flag & CYM_TIMER_FLAG_ACTIVATED) &&
			    (t->parent.flag & CYM_TIMER_FLAG_SOFT_TIMER))
			{
				t->parent.flag &= ~CYM_TIMER_FLAG_ACTIVATED;

				/* restrat the timer */
				cym_timer_start(t);
			}
			else if ((t->parent.flag & CYM_TIMER_FLAG_ONE_SHOT) &&
				 (t->parent.flag & CYM_TIMER_FLAG_SOFT_TIMER))
			{
				/* set deactivated flag */
				t->parent.flag &= ~CYM_TIMER_FLAG_ACTIVATED;
			}
		}
		/* there's no more check in list */
		else
		{
			break;
		}
	}
}
#endif	/* CYM_USING_SOFT_TIMER */

/*@}*/
